--[[ scheme_type: active_section author: ??? modified_by: Alundaio --]] local math = math local function angle_to_direction(oangle) local yaw = oangle.y local pitch = oangle.x return vector():setHP(yaw,pitch):normalize() end --mlr local assoc_tbl = { idle = {director = {"", "_eat_bread", "_eat_kolbasa", "_drink_vodka", "_drink_energy", "_weapon", "_roast_kolbasa", "_roast_kolbasa_bred", "_roast_bred", "_sleep", "_use_pda"}, listener = {"", "_eat_bread", "_eat_kolbasa", "_drink_vodka", "_drink_energy", "_weapon", "_roast_kolbasa", "_roast_kolbasa_bred", "_roast_bred", "_sleep", "_use_pda"}}, harmonica = {director = {"_harmonica"}, listener = {"", "_eat_bread", "_eat_kolbasa", "_drink_vodka", "_drink_energy", "_weapon", "_roast_kolbasa", "_roast_kolbasa_bred", "_roast_bred", "_sleep", "_use_pda"}}, guitar = {director = {"_guitar"}, listener = {"", "_eat_bread", "_eat_kolbasa", "_drink_vodka", "_drink_energy", "_weapon", "_roast_kolbasa", "_roast_kolbasa_bred", "_roast_bred", "_sleep", "_use_pda"}}, story = {director = {"", "_weapon"}, listener = {"", "_eat_bread", "_eat_kolbasa", "_drink_vodka", "_drink_energy", "_weapon", "_roast_kolbasa", "_roast_kolbasa_bred", "_roast_bred", "_sleep", "_use_pda"}}, } -------------------------------------------------------------------------------- -- Animated Point -------------------------------------------------------------------------------- class "evaluator_need_animpoint" (property_evaluator) function evaluator_need_animpoint:__init(storage, name) super(nil, name) self.st = storage end function evaluator_need_animpoint:evaluate() return xr_logic.is_active(self.object, self.st) or false end class "evaluator_reach_animpoint" (property_evaluator) function evaluator_reach_animpoint:__init(storage, name) super(nil, name) self.st = storage end function evaluator_reach_animpoint:evaluate() --utils_data.debug_write("eva_reach_animpoint") if (xr_danger.has_danger(self.object)) then return false end return self.st.animpoint:position_riched() == true end -------------------------------------------------------------------------------- class "action_reach_animpoint" (action_base) function action_reach_animpoint:__init (npc, action_name, storage) super(nil, action_name) self.st = storage end function action_reach_animpoint:initialize() action_base.initialize(self) self.st.animpoint:calculate_position() end function action_reach_animpoint:execute() action_base.execute(self) --utils_data.debug_write("action_reach_animpoint:execute") if not (self.st.animpoint.position_vertex) then return end self.object:set_dest_level_vertex_id(self.st.animpoint.position_vertex) --self.object:set_desired_direction(self.st.animpoint.smart_direction) self.object:set_path_type(game_object.level_path) --printf("%s animpoint position riched %s", npc:name(), npc:position():distance_to_sqr(self.vertex_position)) local reach_movement = xr_logic.pick_section_from_condlist(db.actor, nil, self.st.reach_movement) if (self.object:position():distance_to_sqr(self.st.animpoint.vertex_position) <= self.st.reach_distance) then state_mgr.set_state(self.object, reach_movement, nil, nil, {look_position = self.st.animpoint.look_position} ) else state_mgr.set_state(self.object, reach_movement) end end function action_reach_animpoint:finalize() action_base.finalize(self) self.st.am_i_reached = false end -------------------------------------------------------------------------------- class "action_animpoint" (action_base) function action_animpoint:__init (npc, action_name, storage) super(nil, action_name) self.st = storage end function action_animpoint:initialize() action_base.initialize(self) state_mgr.set_state(self.object,"idle") self.st.animpoint:start() end function action_animpoint:execute() action_base.execute(self) --utils_data.debug_write("action_animpoint:execute") if not(self.st.animpoint.started) then self.st.animpoint:start() -- zatychka end -- printf("%s animpoint action %s", self.object:name(), tostring(self.st.animpoint:get_action())) -- state_mgr.set_state(self.object, self.st.animpoint:get_action(), nil, nil, nil, state_mgr.set_state(self.object, self.st.animpoint:get_action(), nil, nil, {look_position = self.st.animpoint.look_position},{animation_position = self.st.animpoint.position, animation_direction = self.st.animpoint.smart_direction} ) end function action_animpoint:net_destroy(npc) self.st.animpoint:stop() end function action_animpoint:finalize() self.st.animpoint:stop() action_base.finalize(self) end -------------------------------------------------------------------------------- -- Класс отыгрывания разных ништяков на точке -------------------------------------------------------------------------------- class "animpoint" function animpoint:__init(npc, storage) self.npc_id = npc:id() self.st = storage end -- Вызывается при активации схемы function animpoint:initialize() self.camp = nil self.st.base_action = nil self.current_action = nil self.vertex_position = nil self.position = nil self.smart_direction = nil self.look_position = nil self.avail_actions = {} self.st.approved_actions = {} self.st.description = nil self.started = false self.cover_name = nil end function animpoint:activate_scheme(loading, npc, switching_scheme) self.st.signals = {} self:calculate_position() if self.started == true then -- не стопать, если нужно отыграть тот же смарткавер и тот же экшн. (Кешировать в :start()) -- Возможно self.current_action - то, что надо if not self.st.use_camp and self.cover_name == self.st.cover_name then self:fill_approved_actions() local target_action = self.st.approved_actions[math.random(#self.st.approved_actions)].name --printf("check animpoint stop %s == %s", tostring(target_action), tostring(self.current_action)) local current_st_animstate = state_lib.states[target_action].animstate local target_st_animstate = state_lib.states[self.current_action].animstate --printf("check animpoint stop %s == %s", tostring(current_st_animstate), tostring(target_st_animstate)) if current_st_animstate == target_st_animstate then --printf("NO ANIMPOINT STOP %s", npc:name()) -- Если у нас одинаковое состояние тела, но сами дескрипторы анимаций разные, нужно перевыбрать текущее действие if target_action ~= self.current_action then self.current_action = self.st.approved_actions[math.random(#self.st.approved_actions)].name end return end end self:stop() end end --' Рассчитывает позицию, куда идти function animpoint:calculate_position() --utils_data.debug_write("animpoint:calculate_position") local smartcover = se_smart_cover.registered_smartcovers[self.st.cover_name] if smartcover == nil then printf("There is no smart_cover with name [%s]", self.st.cover_name) return end self.position = se_smart_cover.registered_smartcovers[self.st.cover_name].position self.position_vertex = level.vertex_id(self.position) self.vertex_position = level.vertex_position(self.position_vertex) self.smart_direction = self.st.direction or angle_to_direction(smartcover.angle) local look_dir = self.smart_direction:normalize() self.look_position = vector():set(self.vertex_position.x + 10*look_dir.x,self.vertex_position.y,self.vertex_position.z + 10*look_dir.z) -- Также получаем дескриптор позиции, и формируем список доступных действий. local description_name = smartcover:description() -- make them interchangable for variety if (description_name == "animpoint_sit_ass" or description_name == "animpoint_sit_knee" or description_name == "animpoint_sit") then local r = math.random(1,100) description_name = r > 66 and "animpoint_sit_ass" or r > 33 and "animpoint_sit_knee" or "animpoint_sit" end if xr_animpoint_predicates.associations[description_name] == nil then if self.st.avail_animations == nil then printf("Wrong animpoint smart_cover description %s, name %s", tostring(description_name), smartcover:name()) end end self.st.description = description_name self.avail_actions = xr_animpoint_predicates.associations[description_name] self.st.approved_actions = {} end --' Возвращает достиг ли персонаж точки начала работы схемы function animpoint:position_riched() local npc = db.storage[self.npc_id] and db.storage[self.npc_id].object if not (npc) then return false end if not (self.position) then return false end local dist = npc:position():distance_to_sqr(self.position) if (self.current_action and dist <= self.st.reach_distance) then return true end if (dist <= self.st.reach_distance) then local dir1 = vector():set(self.smart_direction) --local dir2 = vector():set(npc:direction()) --npc:set_desired_direction() --npc:set_sight(look.direction, dir1:normalize(), true) state_mgr.set_state(npc, "animpoint_reach", {turn_end_func=function() self.st.am_i_reached = true return end}, nil, {look_dir = dir1}) --[[ local v1 = -math.deg(math.atan2(dir1.x, dir1.z)) local v2 = -math.deg(math.atan2(dir2.x, dir2.z)) local rot_y = math.abs(math.min(math.abs(v1-v2), 360-math.abs(v1)-math.abs(v2))) if (rot_y <= 30) then return true end --]] return self.st.am_i_reached else self.st.am_i_reached = false end return false end function animpoint:fill_approved_actions() local is_in_camp = self.camp ~= nil -- Если задан авеил анимейшнз, то мы игнорим ассоциации с нашим смарткавером. if self.st.avail_animations ~= nil then local t = self.st.approved_actions -- animations are set from custom_data? for k, v in pairs(self.st.avail_animations) do t[#t+1] = {predicate = function() return true end, name = v} end else if self.avail_actions ~= nil then local t = self.st.approved_actions for k,v in pairs(self.avail_actions) do -- Убираем те действия, которые не подходят по прекондишну --printf("checking approved actions %s", self.npc_id) if v.predicate(self.npc_id, is_in_camp)==true then t[#t+1] = v end end end end end -- Чувак дошел до точки и может начинать работать function animpoint:start() local npc = db.storage[self.npc_id] and db.storage[self.npc_id].object if not (npc) then printe("!xr_animpoint: ERROR: NPC don't exist!") return end local state = state_mgr.get_state(npc) local camp = sr_camp.get_current_camp(self.vertex_position) if (camp) then if (self.st.use_camp == true and not string.find(state,"sleep")) then self.camp = camp self.camp:register_npc(self.npc_id) self.in_camp = true elseif (self.st.use_camp == false or string.find(state,"sleep")) then if self.in_camp == true then self.camp:unregister_npc(self.npc_id) self.in_camp = nil self.camp = nil end end elseif (self.in_camp == true and self.camp ~= nil) then self.camp:unregister_npc(npc:id()) self.in_camp = nil self.camp = nil end self:fill_approved_actions() -- Получаем сигнал, какое действие мы можем отыграть: if self.camp ~= nil then self.camp:register_npc(self.npc_id) else if (self.st.approved_actions and #self.st.approved_actions > 0) then self.current_action = self.st.approved_actions[math.random(#self.st.approved_actions)].name else if not (self.st.avail_animations) then return end self.current_action = self.st.avail_animations[1] end end self.started = true self.cover_name = self.st.cover_name end -- Чувак ушел с точки function animpoint:stop() if self.camp ~= nil then self.camp:unregister_npc(self.npc_id) end self.started = false self.current_action = nil end -- Возвращает, что надо делать function animpoint:get_action() return self.current_action end local tmp_actions function animpoint:update() if not (self.vertex_position) then return end if (self.npc_id==nil) then printf("Trying to use destroyed object!") return end tmp_actions = iempty_table(tmp_actions) local size_t = 0 local descr = self.st.description if (self.camp == nil or self.st.use_camp ~= true) then if self.st.avail_animations == nil then if self.st.approved_actions == nil then printf("animpoint not in camp and approved_actions is nil. Name [%s]", self.st.cover_name) return end for k,v in pairs(self.st.approved_actions) do size_t = size_t + 1 tmp_actions[size_t] = v.name end else for k,v in pairs(self.st.avail_animations) do size_t = size_t + 1 tmp_actions[size_t] = v end end self.current_action = tmp_actions[math.random(size_t)] return end local camp_action, is_director = self.camp:get_camp_action(self.npc_id) local tbl = {} if (is_director) then --printf(" [%s] is director", self.npc_id) tbl = assoc_tbl[camp_action].director self.__dtimer = time_global() else self.__dtimer = not self.__dtimer and time_global() + math.random(10000,15000) or self.__dtimer if (self.current_action and time_global() < self.__dtimer) then return end if (not camp_action) then -- TODO: test and follow the cause printf("camp_action for [%s] is nil", self.npc_id) return end --printf(" [%s] is not director", self.npc_id) tbl = assoc_tbl[camp_action].listener end if not (self.st.approved_actions) then printf("animpoint in camp and approved_actions is nil. Name [%s]", self.st.cover_name) return end local found = false for k,v in pairs(self.st.approved_actions) do for i = 1, #tbl do if(descr..tbl[i]==v.name) then --printf(" approved %s", tostring(v.name)) size_t = size_t + 1 tmp_actions[size_t] = v.name found = true end end end if not found then size_t = size_t + 1 tmp_actions[size_t] = descr end local rnd = math.random(size_t) local action = tmp_actions[rnd] if (not action) then printf("! xr_animpoint update | 1 | tmp_actions[%s] not found", rnd) end --action - выбранный экшн из доступных if(self.st.base_action) then if(self.st.base_action==descr.."_weapon") then action = descr.."_weapon" end if(action==descr.."_weapon") and (self.st.base_action==descr) then table.remove(tmp_actions, rnd) local rnd2 = math.random(size_t - 1) -- TODO: see if this what caused the error, keep an eye for any illegal states errors action = tmp_actions[rnd2] if (not action) then printf("! xr_animpoint update | 2 | tmp_actions[%s] not found", rnd2) end end else if(action==descr.."_weapon") then self.st.base_action = action else self.st.base_action = descr end end self.current_action = action local npc = db.storage[self.npc_id] and db.storage[self.npc_id].object if (npc) then state_mgr.set_state(npc, action, nil, nil, {look_position = self.look_position},{animation_position = self.position, animation_direction = self.smart_direction} ) end end -------------------------------------------------------------------------------- -- Smartcover binder -------------------------------------------------------------------------------- function add_to_binder(npc, ini, scheme, section, storage) if not npc then return end local operators = {} local properties = {} local manager = npc:motivation_action_manager() if not (manager) then return end properties["need_animpoint"] = xr_evaluators_id.animpoint_property + 1 properties["reach_animpoint"] = xr_evaluators_id.animpoint_property + 2 properties["state_mgr_logic_active"] = xr_evaluators_id.state_mgr + 4 operators["action_animpoint"] = xr_actions_id.animpoint_action + 1 operators["action_reach_animpoint"] = xr_actions_id.animpoint_action + 2 -- -- evaluators manager:add_evaluator(properties["need_animpoint"], this.evaluator_need_animpoint(storage, "animpoint_need")) manager:add_evaluator(properties["reach_animpoint"], this.evaluator_reach_animpoint(storage, "animpoint_reach")) storage.animpoint = animpoint(npc, storage) -- Зарегистрировать все actions, в которых должен быть вызван метод reset_scheme при изменении настроек схемы: xr_logic.subscribe_action_for_events(npc, storage, storage.animpoint) local new_action = this.action_reach_animpoint(npc, "action_reach_animpoint", storage) if new_action then new_action:add_precondition(world_property(stalker_ids.property_alive, true)) new_action:add_precondition(world_property(stalker_ids.property_enemy,false)) new_action:add_precondition(world_property(stalker_ids.property_danger,false)) new_action:add_precondition(world_property(properties["need_animpoint"], true)) new_action:add_precondition(world_property(properties["reach_animpoint"], false)) modules.add_common_precondition(scheme,new_action) new_action:add_effect (world_property(properties["need_animpoint"], false)) new_action:add_effect (world_property(properties["state_mgr_logic_active"], false)) manager:add_action(operators["action_reach_animpoint"], new_action) xr_logic.subscribe_action_for_events(npc, storage, new_action) end new_action = this.action_animpoint(npc, "action_animpoint", storage) if new_action then new_action:add_precondition(world_property(stalker_ids.property_alive, true)) new_action:add_precondition(world_property(stalker_ids.property_enemy,false)) new_action:add_precondition(world_property(stalker_ids.property_danger,false)) new_action:add_precondition(world_property(properties["need_animpoint"], true)) new_action:add_precondition(world_property(properties["reach_animpoint"], true)) modules.add_common_precondition(scheme,new_action) new_action:add_effect (world_property(properties["need_animpoint"], false)) new_action:add_effect (world_property(properties["state_mgr_logic_active"], false)) manager:add_action(operators["action_animpoint"], new_action) xr_logic.subscribe_action_for_events(npc, storage, new_action) end --printf("ACTION_ALIFE_ID(xr_animpoint.add_to_binder): " .. tostring(xr_actions_id.alife)) new_action = manager:action(xr_actions_id.alife) if new_action then new_action:add_precondition(world_property(properties["need_animpoint"], false)) end end function set_scheme(npc, ini, scheme, section, gulag_name) --printf("DEBUG: %s set_scheme: scheme='%s', section='%s'", npc:name(), scheme, section) local st = xr_logic.assign_storage_and_bind(npc, ini, scheme, section) st.logic = xr_logic.cfg_get_switch_conditions(ini, section, npc) st.cover_name = ini:r_string_ex(section,"cover_name") or "$script_id$_cover" st.use_camp = ini:r_bool_ex(section,"use_camp",true) st.reach_distance = ini:r_float_ex(section,"reach_distance") or 0.8 st.reach_movement = xr_logic.parse_condlist(db.actor, "animpoint", "reach_movement", ini:r_string_ex(section,"reach_movement") or "walk") st.reach_distance = st.reach_distance*st.reach_distance st.direction = ini:r_string_ex(section,"direction") st.direction = st.direction and utils_data.string_to_vector(st.direction) local tmp = ini:r_string_ex(section,"avail_animations") if(tmp~=nil) then st.avail_animations = parse_names(tmp) else st.avail_animations = nil end end